package org.lds.logger.xml;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.lds.logger.config.Configuration;
import org.lds.logger.config.LifeCycle;
import org.lds.logger.config.appender.*;
import org.lds.logger.config.appender.async.AsyncAppenderGroup;
import org.lds.logger.config.appender.rolling.AbstractRollingStrategy;
import org.lds.logger.config.appender.rolling.DefaultRollingStrategy;
import org.lds.logger.config.appender.rolling.RollingStrategy;
import org.lds.logger.config.filter.AbstractAppenderFilter;
import org.lds.logger.config.filter.LevelAppenderFilter;
import org.lds.logger.config.layout.Layout;
import org.lds.logger.config.layout.PatternLayout;
import org.lds.logger.config.level.Level;
import org.lds.logger.util.AssertUtils;
import org.lds.logger.util.PropertyPlaceholderHelper;
import org.lds.logger.util.Utils;

import java.util.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@XStreamAlias("configuration")
public class XMLConfiguration {
    @XStreamImplicit(itemFieldName="property")
    private List<XMLProperty> properties;
    @XStreamOmitField
    private Map<String, XMLProperty> propertyMap;
    @XStreamOmitField
    private Map<String, String> envProperties;

    @XStreamImplicit(itemFieldName="filter")
    private List<XMLFilter> filters;
    @XStreamOmitField
    private Map<String, XMLFilter> filterMap;

    @XStreamImplicit(itemFieldName="layout")
    private List<XMLLayout> layouts;
    @XStreamOmitField
    private Map<String, XMLLayout> layoutMap;

    @XStreamImplicit(itemFieldName="rollingStrategy")
    private List<XMLRollingStrategy> rollingStrategyList;
    @XStreamOmitField
    private Map<String, XMLRollingStrategy> rollingStrategyMap;

    @XStreamImplicit(itemFieldName="appender")
    private List<XMLAppender> appenderList;
    @XStreamOmitField
    private Map<String, XMLAppender> appenderMap;

    @XStreamImplicit(itemFieldName="asyncAppenderGroup")
    private List<XMLAsyncAppenderGroup> asyncAppenderGroups;
    @XStreamOmitField
    private Map<String, XMLAsyncAppenderGroup> asyncAppenderGroupMap;

    @XStreamImplicit(itemFieldName="logger")
    private List<XMLLogger> loggers;
    @XStreamOmitField
    private Map<String, XMLLogger> loggerMap;

    @XStreamAlias("root")
    private XMLRoot root;
    @XStreamOmitField
    private Set<String> appenderNames;

    @XStreamOmitField
    private PropertyPlaceholderHelper propertyPlaceholderHelper;

    public Configuration buildConfiguration() {
        try {
            AssertUtils.assertTrue(root != null, "root not found");

            // 设置属性
            setAttributes();

            return Configuration.builder()
                    .rootLevel(Level.getLevel(root.getLevel()))
                    .asyncAppenderGroup(buildAsyncAppenderGroup())
                    .appenderList(buildAppenderList())
                    .envProperties(envProperties)
                    .loggerLevelMap(buildLoggerLevelMap())
                    .build();
        } catch (Throwable e) {
            Utils.report("logger.xml配置文件XmlConfiguration转Configuration失败. error=" + e.getMessage());
            return null;
        }
    }

    private void setAttributes() {
        this.propertyMap = buildPropertyMap();
        fillEnvProperties();

        this.filterMap = buildFilterMap();
        this.layoutMap = buildLayoutMap();
        this.rollingStrategyMap = buildRollingStrategyMap();
        this.appenderMap = buildAppenderMap();
        this.asyncAppenderGroupMap = buildAsyncAppenderGroupMap();
        this.loggerMap = buildLoggerMap();
        this.appenderNames = buildAppenderNames();
    }

    private Map<String, Level> buildLoggerLevelMap() {
        Map<String, Level> loggerLevelMap = new HashMap<>();
        loggerMap.forEach((name, level) -> {
            loggerLevelMap.put(name, Level.getLevel(level.getLevel()));
        });
        return loggerLevelMap;
    }

    private Map<String, XMLProperty> buildPropertyMap() {
        Map<String, XMLProperty> propertyMap = new HashMap<>();
        if (CollectionUtils.isEmpty(properties)) return propertyMap;

        for (XMLProperty property : properties) {
            if (property != null && StringUtils.isNotEmpty(property.getName()) && StringUtils.isNotEmpty(property.getValue())) {
                propertyMap.put(property.getName(), property);
            }
        }
        return propertyMap;
    }

    private Map<String, XMLFilter> buildFilterMap() {
        Map<String, XMLFilter> filterMap = new HashMap<>();
        if (CollectionUtils.isEmpty(filters)) return filterMap;

        for (XMLFilter filter : filters) {
            if (filter != null &&
                    StringUtils.isNotEmpty(filter.getName()) &&
                    StringUtils.isNotEmpty(filter.getClazz()) &&
                    StringUtils.isNotEmpty(filter.getLevel())) {
                filter.setLevel(propertyPlaceholderHelper.replacePlaceholders(filter.getLevel(), envProperties));
                filterMap.put(filter.getName(), filter);
            }
        }
        return filterMap;
    }

    private Map<String, XMLLayout> buildLayoutMap() {
        Map<String, XMLLayout> layoutMap = new HashMap<>();
        if (CollectionUtils.isEmpty(layouts)) return layoutMap;

        for (XMLLayout layout : layouts) {
            if (layout != null &&
                    StringUtils.isNotEmpty(layout.getName()) &&
                    StringUtils.isNotEmpty(layout.getClazz())) {
                layout.setPattern(propertyPlaceholderHelper.replacePlaceholders(layout.getPattern(), envProperties));
                layout.setCharset(propertyPlaceholderHelper.replacePlaceholders(layout.getCharset(), envProperties));
                layoutMap.put(layout.getName(), layout);
            }
        }
        return layoutMap;
    }

    private Map<String, XMLRollingStrategy> buildRollingStrategyMap() {
        Map<String, XMLRollingStrategy> rollingStrategyMap = new HashMap<>();
        if (CollectionUtils.isEmpty(rollingStrategyList)) return rollingStrategyMap;

        for (XMLRollingStrategy rollingStrategy : rollingStrategyList) {
            if (rollingStrategy != null &&
                    StringUtils.isNotEmpty(rollingStrategy.getName()) &&
                    StringUtils.isNotEmpty(rollingStrategy.getClazz())) {
                rollingStrategy.setMaxFileSize(propertyPlaceholderHelper.replacePlaceholders(rollingStrategy.getMaxFileSize(), envProperties));
                rollingStrategy.setMaxFileCount(propertyPlaceholderHelper.replacePlaceholders(rollingStrategy.getMaxFileCount(), envProperties));
                rollingStrategy.setMaxFileReserveDayCount(propertyPlaceholderHelper.replacePlaceholders(rollingStrategy.getMaxFileReserveDayCount(), envProperties));
                rollingStrategyMap.put(rollingStrategy.getName(), rollingStrategy);
            }
        }
        return rollingStrategyMap;
    }

    private Map<String, XMLAppender> buildAppenderMap() {
        Map<String, XMLAppender> appenderMap = new HashMap<>();
        if (CollectionUtils.isEmpty(appenderList)) return appenderMap;

        for (XMLAppender appender : appenderList) {
            if (appender != null &&
                    StringUtils.isNotEmpty(appender.getName()) &&
                    StringUtils.isNotEmpty(appender.getClazz())) {
                appender.setPriority(propertyPlaceholderHelper.replacePlaceholders(appender.getPriority(), envProperties));
                appender.setDestination(propertyPlaceholderHelper.replacePlaceholders(appender.getDestination(), envProperties));
                appenderMap.put(appender.getName(), appender);
            }
        }
        return appenderMap;
    }

    private Map<String, XMLLogger> buildLoggerMap() {
        Map<String, XMLLogger> loggerMap = new HashMap<>();
        if (CollectionUtils.isEmpty(loggers)) return loggerMap;

        for (XMLLogger logger : loggers) {
            if (logger != null &&
                    StringUtils.isNotEmpty(logger.getName()) &&
                    StringUtils.isNotEmpty(logger.getLevel())) {
                logger.setLevel(propertyPlaceholderHelper.replacePlaceholders(logger.getLevel(), envProperties));
                loggerMap.put(logger.getName(), logger);
            }
        }
        return loggerMap;
    }

    private Map<String, XMLAsyncAppenderGroup> buildAsyncAppenderGroupMap() {
        Map<String, XMLAsyncAppenderGroup> asyncAppenderGroupMap = new HashMap<>();
        if (CollectionUtils.isEmpty(asyncAppenderGroups)) return asyncAppenderGroupMap;

        for (XMLAsyncAppenderGroup asyncAppenderGroup : asyncAppenderGroups) {
            if (asyncAppenderGroup != null &&
                    StringUtils.isNotEmpty(asyncAppenderGroup.getName()) &&
                    StringUtils.isNotEmpty(asyncAppenderGroup.getClazz()) &&
                    CollectionUtils.isNotEmpty(asyncAppenderGroup.getAsyncAppenderRef())) {
                asyncAppenderGroup.setQueueSize(propertyPlaceholderHelper.replacePlaceholders(asyncAppenderGroup.getQueueSize(), envProperties));
                asyncAppenderGroupMap.put(asyncAppenderGroup.getName(), asyncAppenderGroup);
            }
        }
        return asyncAppenderGroupMap;
    }

    private void fillEnvProperties() {
        propertyPlaceholderHelper = PropertyPlaceholderHelper.createDefaultPropertyPlaceholderHelper();
        envProperties = new HashMap<>();
        if (MapUtils.isEmpty(propertyMap)) return;

        List<XMLProperty> need = new ArrayList<>();
        List<XMLProperty> noNeed = new ArrayList<>();

        propertyMap.forEach((name, property) -> {
            if (property.getValue().contains("${")) {
                need.add(property);
            } else {
                noNeed.add(property);
            }
        });

        for (XMLProperty property : noNeed) {
            envProperties.put(property.getName(), property.getValue());
        }

        // 需要注意配置文件属性设置的顺序
        for (XMLProperty property : need) {
            String value = property.getValue();
            envProperties.put(property.getName(), propertyPlaceholderHelper.replacePlaceholders(value, envProperties));
        }
    }

    private AsyncAppenderGroup buildAsyncAppenderGroup() {
        List<XMLAsyncAppenderGroup> xmlAsyncAppenderGroups = getValidXMLAsyncAppenderGroups();
        if (CollectionUtils.isEmpty(xmlAsyncAppenderGroups)) return null;

        for (XMLAsyncAppenderGroup xmlAsyncAppenderGroup : xmlAsyncAppenderGroups) {
            List<XMLAppenderReference> appenderRefs = xmlAsyncAppenderGroup.getAsyncAppenderRef();
            List<XMLAppender> asyncXMLAppenderList = getValidAsyncXMLAppenderList(appenderRefs);
            if (CollectionUtils.isEmpty(asyncXMLAppenderList)) continue;

            List<Appender> appenderList = getAppenderList(asyncXMLAppenderList);
            if (CollectionUtils.isEmpty(appenderList)) continue;

            AsyncAppenderGroup asyncAppenderGroup = createAsyncAppenderGroupInstance(xmlAsyncAppenderGroup.getClazz());
            if (asyncAppenderGroup == null) continue;

            asyncAppenderGroup.setQueueSize(buildQueueSize(xmlAsyncAppenderGroup.getQueueSize()));
            asyncAppenderGroup.setAsyncAppenderList(appenderList);

            if (asyncAppenderGroup instanceof LifeCycle) {
                try {
                    ((LifeCycle)asyncAppenderGroup).initialize();
                } catch (Throwable e) {
                    Utils.report("asyncAppenderGroup initialize fail. error=" + e.getMessage());
                    continue;
                }
            }

            // 优先异步appender
            appenderNames.remove(xmlAsyncAppenderGroup.getName());
            for (Appender appender : appenderList) {
                appenderNames.remove(appender.getName());
            }
            return asyncAppenderGroup;
        }
        return null;
    }

    private int buildQueueSize(String queueSize) {
        try {
            Integer size = Integer.valueOf(queueSize);
            if (size < 100 || size > 10000) return 500;
            return size;
        } catch (Throwable e) {
            return 500;
        }
    }

    private List<Appender> getAppenderList(List<XMLAppender> xmlAppenderList) {
        List<Appender> appenderList = new ArrayList<>();
        for (XMLAppender xmlAppender : xmlAppenderList) {
            try {
                Appender appender = buildAppender(xmlAppender);
                if (appender != null) {
                    appenderList.add(appender);
                }
            } catch (Throwable e) {
                Utils.report("buildAppender fail. error=" + e.getMessage());
            }
        }

        if (appenderList.size() > 1) {
            Collections.sort(appenderList, (o1, o2) -> o2.getPriority() - o1.getPriority());
        }

        return appenderList;
    }

    private List<Appender> buildAppenderList() {
        List<XMLAppender> xmlAppenderList = getValidXMLAppenderList();
        List<Appender> appenderList = getAppenderList(xmlAppenderList);
        AssertUtils.assertTrue(CollectionUtils.isNotEmpty(appenderList), "appender not found");
        return appenderList;
    }

    /**
     * 包含异步和同步两种appender
     */
    private Set<String> buildAppenderNames() {
        Set<String> appenderNames = new HashSet<>();
        List<XMLAppenderReference> xmlAppenderReferences = root.getXMLAppenderReferences();
        if (CollectionUtils.isEmpty(xmlAppenderReferences)) return appenderNames;
        for (XMLAppenderReference xmlAppenderReference : xmlAppenderReferences) {
            String reference = xmlAppenderReference.getReference();
            if (StringUtils.isNotEmpty(reference)) {
                appenderNames.add(reference);
            }
        }
        return appenderNames;
    }

    private Appender buildAppender(XMLAppender xmlAppender) {
        AssertUtils.assertTrue(StringUtils.isNotEmpty(xmlAppender.getName()), "appender:name not found");

        AbstractAppender appender = createAppenderInstance(xmlAppender.getClazz());
        if (appender == null) return null;

        if (!(appender instanceof ConsoleAppender)) {
            AssertUtils.assertTrue(StringUtils.isNotEmpty(xmlAppender.getDestination()), "appender["+xmlAppender.getName()+"]:destination not found");
        }

        appender.setName(xmlAppender.getName());
        appender.setDestination(xmlAppender.getDestination());
        appender.setPriority(buildPriority(xmlAppender.getPriority()));
        appender.setAppenderFilter(buildAppenderFilter(xmlAppender.getFilterRef()));
        appender.setLayout(buildLayout(xmlAppender.getLayoutRef()));

        appender.initialize();

        // 在appender.initialize()之后执行,保证文件已被创建
        if (appender instanceof RollingFileAppender) {
            RollingFileAppender fileAppender = (RollingFileAppender) appender;
            fileAppender.setRollingStrategy(buildRollingStrategy(xmlAppender.getRollingStrategyRef(), fileAppender));
        }

        return appender;
    }

    private AbstractAppenderFilter buildAppenderFilter(String filterRef) {
        if (StringUtils.isEmpty(filterRef)) return new LevelAppenderFilter(Level.DEBUG);

        XMLFilter xmlFilter = filterMap.get(filterRef);
        if (xmlFilter == null) return new LevelAppenderFilter(Level.DEBUG);

        AbstractAppenderFilter appenderFilter = createAppenderFilterInstance(xmlFilter.getClazz());
        if (appenderFilter == null) return new LevelAppenderFilter(Level.DEBUG);

        if (appenderFilter instanceof LevelAppenderFilter) {
            ((LevelAppenderFilter) appenderFilter).setLevel(Level.getLevel(xmlFilter.getLevel()));
        }

        try {
            appenderFilter.initialize();
        } catch (Throwable e) {
            return new LevelAppenderFilter(Level.DEBUG);
        }

        return appenderFilter;
    }

    private Layout buildLayout(String layoutRef) {
        if (StringUtils.isEmpty(layoutRef)) return new PatternLayout();

        XMLLayout xmlLayout = layoutMap.get(layoutRef);
        if (xmlLayout == null) return new PatternLayout();

        Layout layout = createLayoutInstance(xmlLayout.getClazz());
        if (layout == null) return new PatternLayout();

        if (layout instanceof PatternLayout) {
            PatternLayout patternLayout = (PatternLayout) layout;
            patternLayout.setPattern(PatternLayout.buildPattern(xmlLayout.getPattern()));
            patternLayout.setCharset(PatternLayout.buildCharset(xmlLayout.getCharset()));
        }

        if (layout instanceof LifeCycle) {
            try {
                ((LifeCycle)layout).initialize();
            } catch (Throwable e) {
                return new PatternLayout();
            }
        }

        return layout;
    }

    private RollingStrategy buildRollingStrategy(String rollingStrategyRef, FileAppender fileAppender) {
        if (StringUtils.isEmpty(rollingStrategyRef)) return new DefaultRollingStrategy();

        XMLRollingStrategy xmlRollingStrategy = rollingStrategyMap.get(rollingStrategyRef);
        if (xmlRollingStrategy == null) return new DefaultRollingStrategy();

        RollingStrategy rollingStrategy = createRollingStrategyInstance(xmlRollingStrategy.getClazz());
        if (rollingStrategy == null) return new DefaultRollingStrategy();

        if (rollingStrategy instanceof AbstractRollingStrategy) {
            AbstractRollingStrategy abstractRollingStrategy = (AbstractRollingStrategy) rollingStrategy;
            abstractRollingStrategy.setFileAppender(fileAppender);
        }

        if (rollingStrategy instanceof DefaultRollingStrategy) {
            DefaultRollingStrategy defaultRollingStrategy = (DefaultRollingStrategy) rollingStrategy;
            defaultRollingStrategy.setMaxFileSize(DefaultRollingStrategy.buildMaxFileSize(xmlRollingStrategy.getMaxFileSize()));
            defaultRollingStrategy.setMaxAllFileSize(DefaultRollingStrategy.buildMaxAllFileSize(xmlRollingStrategy.getMaxAllFileSize()));
            defaultRollingStrategy.setMaxFileCount(DefaultRollingStrategy.buildMaxFileCount(xmlRollingStrategy.getMaxFileCount()));
            defaultRollingStrategy.setMaxFileReserveDayCount(DefaultRollingStrategy.buildMaxFileReserveDayCount(xmlRollingStrategy.getMaxFileReserveDayCount()));
            defaultRollingStrategy.setCompressType(DefaultRollingStrategy.buildCompressType(xmlRollingStrategy.getCompressType()));
        }

        if (rollingStrategy instanceof LifeCycle) {
            try {
                ((LifeCycle)rollingStrategy).initialize();
            } catch (Throwable e) {
                return new DefaultRollingStrategy();
            }
        }

        return rollingStrategy;
    }

    private AsyncAppenderGroup createAsyncAppenderGroupInstance(String className) {
        try {
            AsyncAppenderGroup asyncAppenderGroup = (AsyncAppenderGroup) Class.forName(className).newInstance();
            return asyncAppenderGroup;
        } catch (Throwable e) {
            Utils.report("createInstance AsyncAppenderGroup fail. error=" + e.getMessage());
            return null;
        }
    }

    private AbstractAppender createAppenderInstance(String className) {
        AssertUtils.assertTrue(StringUtils.isNotEmpty(className), "appender:clazz not found");

        try {
            AbstractAppender appender = (AbstractAppender) Class.forName(className).newInstance();
            return appender;
        } catch (Throwable e) {
            Utils.report("createInstance Appender fail. error=" + e.getMessage());
            return null;
        }
    }

    private AbstractAppenderFilter createAppenderFilterInstance(String className) {
        AssertUtils.assertTrue(StringUtils.isNotEmpty(className), "AppenderFilter:clazz not found");

        try {
            AbstractAppenderFilter abstractAppenderFilter = (AbstractAppenderFilter) Class.forName(className).newInstance();
            return abstractAppenderFilter;
        } catch (Throwable e) {
            Utils.report("createInstance AppenderFilter fail. error=" + e.getMessage());
            return null;
        }
    }

    private Layout createLayoutInstance(String className) {
        AssertUtils.assertTrue(StringUtils.isNotEmpty(className), "Layout:clazz not found");

        try {
            Layout layout = (Layout) Class.forName(className).newInstance();
            return layout;
        } catch (Throwable e) {
            Utils.report("createInstance Layout fail. error=" + e.getMessage());
            return null;
        }
    }

    private RollingStrategy createRollingStrategyInstance(String className) {
        AssertUtils.assertTrue(StringUtils.isNotEmpty(className), "RollingStrategy:clazz not found");

        try {
            RollingStrategy rollingStrategy = (RollingStrategy) Class.forName(className).newInstance();
            return rollingStrategy;
        } catch (Throwable e) {
            Utils.report("createInstance RollingStrategy fail. error=" + e.getMessage());
            return null;
        }
    }

    private int buildPriority(String priority) {
        if (StringUtils.isEmpty(priority)) return 0;
        try {
            return Integer.valueOf(priority);
        } catch (Throwable e) {
            return 0;
        }
    }

    private List<XMLAsyncAppenderGroup> getValidXMLAsyncAppenderGroups() {
        List<XMLAsyncAppenderGroup> validXMLAsyncAppenderGroups = new ArrayList<>();
        if (appenderNames.isEmpty()) return validXMLAsyncAppenderGroups;

        for (String appenderName : appenderNames) {
            XMLAsyncAppenderGroup asyncAppenderGroup = asyncAppenderGroupMap.get(appenderName);
            if (asyncAppenderGroup != null) {
                validXMLAsyncAppenderGroups.add(asyncAppenderGroup);
            }
        }

        return validXMLAsyncAppenderGroups;
    }

    private List<XMLAppender> getValidAsyncXMLAppenderList(List<XMLAppenderReference> appenderRefs) {
        if (CollectionUtils.isEmpty(appenderRefs)) return null;

        List<XMLAppender> validAsyncXMLAppenderList = new ArrayList<>();
        for (XMLAppenderReference appenderRef : appenderRefs) {
            XMLAppender xmlAppender = appenderMap.get(appenderRef.getReference());
            if (xmlAppender != null) {
                validAsyncXMLAppenderList.add(xmlAppender);
            }
        }

        return validAsyncXMLAppenderList;
    }

    private List<XMLAppender> getValidXMLAppenderList() {
        List<XMLAppender> validXMLAppenderList = new ArrayList<>();
        if (appenderNames.isEmpty()) return validXMLAppenderList;

        for (String appenderName : appenderNames) {
            XMLAppender xmlAppender = appenderMap.get(appenderName);
            if (xmlAppender != null) {
                validXMLAppenderList.add(xmlAppender);
            }
        }

        return validXMLAppenderList;
    }

}
